{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "99cea58c-48bc-4af6-8358-df9695659983",
   "metadata": {
    "tags": []
   },
   "source": [
    "# Build your own OpenAI Agent"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "673df1fe-eb6c-46ea-9a73-a96e7ae7942e",
   "metadata": {
    "tags": []
   },
   "source": [
    "With the [new OpenAI API](https://openai.com/blog/function-calling-and-other-api-updates) that supports function calling, it's never been easier to build your own agent!\n",
    "\n",
    "In this notebook tutorial, we showcase how to write your own OpenAI agent in **under 50 lines of code**! It is minimal, yet feature complete (with ability to carry on a conversation and use tools)."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "54b7bc2e-606f-411a-9490-fcfab9236dfc",
   "metadata": {
    "tags": []
   },
   "source": [
    "## Initial Setup "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "23e80e5b-aaee-4f23-b338-7ae62b08141f",
   "metadata": {},
   "source": [
    "Let's start by importing some simple building blocks.  \n",
    "\n",
    "The main thing we need is:\n",
    "1. the OpenAI API (using our own `llama_index` LLM class)\n",
    "2. a place to keep conversation history \n",
    "3. a definition for tools that our agent can use."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "9d47283b-025e-4874-88ed-76245b22f82e",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import json\n",
    "from typing import Sequence, List\n",
    "\n",
    "from llama_index.llms import OpenAI, ChatMessage\n",
    "from llama_index.tools import BaseTool, FunctionTool\n",
    "\n",
    "import nest_asyncio\n",
    "\n",
    "nest_asyncio.apply()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6fe08eb1-e638-4c00-9103-5c305bfacccf",
   "metadata": {},
   "source": [
    "Let's define some very simple calculator tools for our agent."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "3dd3c4a6-f3e0-46f9-ad3b-7ba57d1bc992",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def multiply(a: int, b: int) -> int:\n",
    "    \"\"\"Multiple two integers and returns the result integer\"\"\"\n",
    "    return a * b\n",
    "\n",
    "\n",
    "multiply_tool = FunctionTool.from_defaults(fn=multiply)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "bfcfb78b-7d4f-48d9-8d4c-ffcded23e7ac",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def add(a: int, b: int) -> int:\n",
    "    \"\"\"Add two integers and returns the result integer\"\"\"\n",
    "    return a + b\n",
    "\n",
    "\n",
    "add_tool = FunctionTool.from_defaults(fn=add)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fbcbd5ea-f377-44a0-a492-4568daa8b0b6",
   "metadata": {
    "tags": []
   },
   "source": [
    "## Agent Definition"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5b737e6c-64eb-4ae6-a8f7-350b1953e612",
   "metadata": {},
   "source": [
    "Now, we define our agent that's capable of holding a conversation and calling tools in **under 50 lines of code**.\n",
    "\n",
    "The meat of the agent logic is in the `chat` method. At a high-level, there are 3 steps:\n",
    "1. Call OpenAI to decide which tool (if any) to call and with what arguments.\n",
    "2. Call the tool with the arguments to obtain an output\n",
    "3. Call OpenAI to synthesize a response from the conversation context and the tool output.\n",
    "\n",
    "The `reset` method simply resets the conversation context, so we can start another conversation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "a0e068f7-fd24-4f74-8243-5e6e4840f7a6",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "class YourOpenAIAgent:\n",
    "    def __init__(\n",
    "        self,\n",
    "        tools: Sequence[BaseTool] = [],\n",
    "        llm: OpenAI = OpenAI(temperature=0, model=\"gpt-3.5-turbo-0613\"),\n",
    "        chat_history: List[ChatMessage] = [],\n",
    "    ) -> None:\n",
    "        self._llm = llm\n",
    "        self._tools = {tool.metadata.name: tool for tool in tools}\n",
    "        self._chat_history = chat_history\n",
    "\n",
    "    def reset(self) -> None:\n",
    "        self._chat_history = []\n",
    "\n",
    "    def chat(self, message: str) -> str:\n",
    "        chat_history = self._chat_history\n",
    "        chat_history.append(ChatMessage(role=\"user\", content=message))\n",
    "        functions = [\n",
    "            tool.metadata.to_openai_function() for _, tool in self._tools.items()\n",
    "        ]\n",
    "\n",
    "        ai_message = self._llm.chat(chat_history, functions=functions).message\n",
    "        chat_history.append(ai_message)\n",
    "\n",
    "        function_call = ai_message.additional_kwargs.get(\"function_call\", None)\n",
    "        if function_call is not None:\n",
    "            function_message = self._call_function(function_call)\n",
    "            chat_history.append(function_message)\n",
    "            ai_message = self._llm.chat(chat_history).message\n",
    "            chat_history.append(ai_message)\n",
    "\n",
    "        return ai_message.content\n",
    "\n",
    "    def _call_function(self, function_call: dict) -> ChatMessage:\n",
    "        tool = self._tools[function_call[\"name\"]]\n",
    "        output = tool(**json.loads(function_call[\"arguments\"]))\n",
    "        return ChatMessage(\n",
    "            name=function_call[\"name\"],\n",
    "            content=str(output),\n",
    "            role=\"function\",\n",
    "            additional_kwargs={\"name\": function_call[\"name\"]},\n",
    "        )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fbc2cec5-6cc0-4814-92a1-ca0bd237528f",
   "metadata": {},
   "source": [
    "## Let's Try It Out!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "08928f6e-610c-420b-8a7b-7a7042bbd6c6",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "agent = YourOpenAIAgent(tools=[multiply_tool, add_tool])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "d5cefbad-32c4-4273-807a-cc179bae4473",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Hello! How can I assist you today?'"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "agent.chat(\"Hi\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "b8f7650d-57b8-4ef4-b19d-651281ddb1be",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'The product of 2123 multiplied by 215123 is 456,706,129.'"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "agent.chat(\"What is 2123 * 215123\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "707d30b8-6405-4187-a9ed-6146dcc42167",
   "metadata": {
    "tags": []
   },
   "source": [
    "## Our (Slightly Better) `OpenAIAgent` Implementation "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "798ca3fd-6711-4c0c-a853-d868dd14b484",
   "metadata": {},
   "source": [
    "We provide a (slightly better) `OpenAIAgent` implementation in LlamaIndex, which you can directly use as follows.  \n",
    "\n",
    "In comparison to the simplified version above:\n",
    "* it implements the `BaseChatEngine` and `BaseQueryEngine` interface, so you can more seamlessly use it in the LlamaIndex framework. \n",
    "* it supports multiple function calls per conversation turn\n",
    "* it supports streaming\n",
    "* it supports async endpoints\n",
    "* it supports callback and tracing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "38ab3938-1138-43ea-b085-f430b42f5377",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from llama_index.agent import OpenAIAgent\n",
    "from llama_index.llms import OpenAI"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "d852ece7-e5a1-4368-9d59-c7014e0b5b4d",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "llm = OpenAI(model=\"gpt-3.5-turbo-0613\")\n",
    "agent = OpenAIAgent.from_tools([multiply_tool, add_tool], llm=llm, verbose=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "500cbee4",
   "metadata": {},
   "source": [
    "### Chat"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "9fd1cad5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=== Calling Function ===\n",
      "Calling function: multiply with args: {\n",
      "  \"a\": 121,\n",
      "  \"b\": 3\n",
      "}\n",
      "Got output: 363\n",
      "========================\n",
      "=== Calling Function ===\n",
      "Calling function: add with args: {\n",
      "  \"a\": 363,\n",
      "  \"b\": 42\n",
      "}\n",
      "Got output: 405\n",
      "========================\n",
      "(121 * 3) + 42 is equal to 405.\n"
     ]
    }
   ],
   "source": [
    "response = agent.chat(\"What is (121 * 3) + 42?\")\n",
    "print(str(response))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "538bf32f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[ToolOutput(content='363', tool_name='multiply', raw_input={'args': (), 'kwargs': {'a': 121, 'b': 3}}, raw_output=363), ToolOutput(content='405', tool_name='add', raw_input={'args': (), 'kwargs': {'a': 363, 'b': 42}}, raw_output=405)]\n"
     ]
    }
   ],
   "source": [
    "# inspect sources\n",
    "print(response.sources)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fb33983c",
   "metadata": {},
   "source": [
    "### Async Chat"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "1d1fc974",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=== Calling Function ===\n",
      "Calling function: multiply with args: {\n",
      "  \"a\": 121,\n",
      "  \"b\": 3\n",
      "}\n",
      "Got output: 363\n",
      "========================\n",
      "121 * 3 is equal to 363.\n"
     ]
    }
   ],
   "source": [
    "response = await agent.achat(\"What is 121 * 3?\")\n",
    "print(str(response))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aae035cb",
   "metadata": {},
   "source": [
    "### Streaming Chat\n",
    "Here, every LLM response is returned as a generator. You can stream every incremental step, or only the last response."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "14217fb2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=== Calling Function ===\n",
      "Calling function: multiply with args: {\n",
      "  \"a\": 121,\n",
      "  \"b\": 2\n",
      "}\n",
      "Got output: 242\n",
      "========================\n",
      "121 * 2 is equal to 242.\n",
      "\n",
      "Once upon a time, in a small village, there was a group of mice who lived happily in a cozy little burrow. The leader of the group was a wise and courageous mouse named Milo. Milo was known for his intelligence and his ability to solve problems.\n",
      "\n",
      "One day, Milo gathered all the mice together and announced that they needed to find a new home. Their current burrow had become overcrowded, and they needed more space to live comfortably. The mice were excited about the idea of exploring new territories.\n",
      "\n",
      "With their tiny paws and keen senses, the mice set out on their journey. They traveled through fields, forests, and streams, searching for the perfect place to call home. Along the way, they encountered various challenges, such as crossing treacherous rivers and avoiding hungry predators.\n",
      "\n",
      "After days of searching, the mice stumbled upon a hidden meadow surrounded by tall grass and blooming flowers. It was a peaceful and serene place, far away from the hustle and bustle of the village. The mice knew they had found their new home.\n",
      "\n",
      "Using their collective strength and determination, the mice began building their new burrow. They dug tunnels and created intricate chambers, ensuring that each mouse had enough space to live comfortably. Milo, with his exceptional leadership skills, organized the mice into different teams, assigning tasks to each member.\n",
      "\n",
      "As the mice settled into their new home, they realized that they had created a harmonious community. They worked together, sharing food, and looking out for one another. Milo's wisdom and guidance helped them overcome any obstacles they faced.\n",
      "\n",
      "The mice flourished in their new meadow, living happily ever after. They grew in numbers and became known as the Meadow Mice, admired by other animals for their unity and resilience. Milo's legacy lived on, as he continued to lead and inspire the mice for generations to come.\n",
      "\n",
      "And so, the story of the group of mice who found their new home after multiplying their efforts by 121 * 2 became a tale of courage, teamwork, and the power of determination."
     ]
    }
   ],
   "source": [
    "response = agent.stream_chat(\n",
    "    \"What is 121 * 2? Once you have the answer, use that number to write a story about a group of mice.\"\n",
    ")\n",
    "\n",
    "response_gen = response.response_gen\n",
    "\n",
    "for token in response_gen:\n",
    "    print(token, end=\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3fac119f",
   "metadata": {},
   "source": [
    "### Async Streaming Chat"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "33ea069f-819b-4ec1-a93c-fcbaacb362a1",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "=== Calling Function ===\n",
      "Calling function: add with args: {\n",
      "  \"a\": 121,\n",
      "  \"b\": 8\n",
      "}\n",
      "Got output: 129\n",
      "========================\n",
      "121 + 8 is equal to 129.\n",
      "\n",
      "Once upon a time, in a lush green forest, there was a group of mice who lived in harmony. They were known as the Forest Friends, and their leader was a wise and kind-hearted mouse named Oliver.\n",
      "\n",
      "One sunny day, as the mice were going about their daily activities, they stumbled upon a mysterious object hidden beneath a pile of leaves. It was a magical acorn, shimmering with a golden glow. The acorn had the power to grant a wish to whoever possessed it.\n",
      "\n",
      "Excited by the discovery, Oliver gathered all the mice together and shared the news. They decided to use the wish to make their forest home even more beautiful and abundant. With their hearts filled with hope, they held the magical acorn and made their wish.\n",
      "\n",
      "As the mice closed their eyes and made their wish, a gentle breeze swept through the forest. When they opened their eyes, they couldn't believe what they saw. The forest had transformed into a magical wonderland, with vibrant flowers, sparkling streams, and towering trees that reached the sky.\n",
      "\n",
      "The mice explored their enchanted forest, marveling at the beauty that surrounded them. The streams were filled with crystal-clear water, teeming with fish and other aquatic creatures. The trees bore fruits of all kinds, providing an abundance of food for the mice and other forest animals.\n",
      "\n",
      "With their newfound paradise, the Forest Friends thrived. They lived in harmony with nature, sharing their blessings with other creatures. Oliver, as their wise leader, ensured that everyone had enough food and shelter. The mice worked together, building cozy burrows and gathering food for the winter.\n",
      "\n",
      "Word of the magical forest spread far and wide, attracting animals from all corners of the land. The Forest Friends welcomed them with open arms, creating a diverse and vibrant community. The mice, with their kind hearts and generous spirits, became known as the Guardians of the Enchanted Forest.\n",
      "\n",
      "As time passed, the Forest Friends continued to cherish their magical home. They lived in peace and harmony, always grateful for the gift they had received. Oliver, the wise leader, taught the younger mice the importance of unity and respect for nature.\n",
      "\n",
      "And so, the story of the group of mice who discovered a magical acorn and transformed their forest home after adding their efforts by 121 + 8 became a tale of hope, gratitude, and the power of a shared dream. The Forest Friends lived happily ever after, forever grateful for the magic that had brought them together."
     ]
    }
   ],
   "source": [
    "response = await agent.astream_chat(\n",
    "    \"What is 121 + 8? Once you have the answer, use that number to write a story about a group of mice.\"\n",
    ")\n",
    "\n",
    "response_gen = response.response_gen\n",
    "\n",
    "async for token in response.async_response_gen():\n",
    "    print(token, end=\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2fe399c5-6d07-4926-b701-b612efd56b30",
   "metadata": {},
   "source": [
    "### Agent with Personality"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8b47c034-f948-4604-a8d8-828b617ea245",
   "metadata": {},
   "source": [
    "You can specify a system prompt to give the agent additional instruction or personality."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "bef36d1e-c26e-4b07-b3d0-3b7f314a45f5",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from llama_index.agent import OpenAIAgent\n",
    "from llama_index.llms import OpenAI\n",
    "from llama_index.prompts.system import SHAKESPEARE_WRITING_ASSISTANT"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "eba7fa46-1173-42f2-885c-0cc28df1cd2e",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "llm = OpenAI(model=\"gpt-3.5-turbo-0613\")\n",
    "\n",
    "agent = OpenAIAgent.from_tools(\n",
    "    [multiply_tool, add_tool],\n",
    "    llm=llm,\n",
    "    verbose=True,\n",
    "    system_prompt=SHAKESPEARE_WRITING_ASSISTANT,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "c4841778-7008-4b61-afcc-995b6b64e91a",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Greetings, fair traveler! How may I assist thee on this fine day?\n"
     ]
    }
   ],
   "source": [
    "response = agent.chat(\"Hi\")\n",
    "print(response)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "46a83768-1203-4485-a346-2fa78089afb1",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Of course, dear friend! Allow me to weave a tale for thee in the style of Shakespeare. \n",
      "\n",
      "Once upon a time, in a land far away, there lived a noble knight named Sir William. He was known throughout the kingdom for his bravery and chivalry. One fateful day, as Sir William rode through the enchanted forest, he stumbled upon a hidden glade.\n",
      "\n",
      "In the glade, he discovered a beautiful maiden named Lady Rosalind. She was fair of face and gentle of heart, and Sir William was instantly captivated by her beauty. They spent hours conversing, sharing stories, and laughing together.\n",
      "\n",
      "As the days turned into weeks, Sir William and Lady Rosalind's bond grew stronger. They found solace in each other's company and discovered a love that was pure and true. However, their happiness was short-lived, for an evil sorcerer named Malachi had set his sights on Lady Rosalind.\n",
      "\n",
      "Malachi, consumed by jealousy and darkness, sought to claim Lady Rosalind for himself. He devised a wicked plan to separate the two lovers and cast a spell upon Sir William, turning him into a statue of stone. Lady Rosalind, heartbroken and determined, vowed to find a way to break the curse and save her beloved.\n",
      "\n",
      "With unwavering courage, Lady Rosalind embarked on a perilous journey to seek the help of a wise old wizard. She traveled through treacherous mountains, crossed raging rivers, and faced many trials along the way. Finally, after much hardship, she reached the wizard's humble abode.\n",
      "\n",
      "The wizard, known as Merlin, listened to Lady Rosalind's tale of love and woe. He sympathized with her plight and agreed to aid her in breaking the curse. Together, they devised a plan to confront Malachi and restore Sir William to his human form.\n",
      "\n",
      "On the eve of the full moon, Lady Rosalind and Merlin ventured into the heart of Malachi's lair. They faced countless obstacles and battled fierce creatures, but their determination never wavered. Finally, they reached the chamber where Sir William stood, frozen in stone.\n",
      "\n",
      "With a wave of his staff and a powerful incantation, Merlin shattered the curse that held Sir William captive. As the first rays of dawn broke through the darkness, Sir William's eyes fluttered open, and he beheld Lady Rosalind standing before him.\n",
      "\n",
      "Their love, stronger than ever, triumphed over the forces of evil. Sir William and Lady Rosalind returned to the kingdom, where they were hailed as heroes. They lived a long and joyous life together, their love serving as a beacon of hope for all who heard their tale.\n",
      "\n",
      "And so, dear friend, ends the story of Sir William and Lady Rosalind, a tale of love, bravery, and the power of true devotion. May it inspire thee to seek love and adventure in thy own journey through life.\n"
     ]
    }
   ],
   "source": [
    "response = agent.chat(\"Tell me a story\")\n",
    "print(response)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
